/*
 * Copyright 2011 myTDev.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package com.mytdev.commons.security;

import java.io.BufferedInputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.security.NoSuchProviderException;
import java.security.Provider;

/**
 *
 * @author Romain Absil, Yann D'Isanto
 */
public class Hasher {

    private MessageDigest algorithm;
    /** 
     * SHA-1 algorithm. 
     **/
    public static final String SHA1 = "SHA-1";
    /** 
     * SHA-256 algorithm. 
     **/
    public static final String SHA256 = "SHA-256";
    /**
     * SHA-384 algorithm. 
     **/
    public static final String SHA384 = "SHA-384";
    /**
     * SHA-512 algorithm. 
     **/
    public static final String SHA512 = "SHA-512";
    /** 
     * MD2 algorithm. 
     **/
    public static final String MD2 = "MD2";
    /** 
     * MD5 algorithm. 
     **/
    public static final String MD5 = "MD5";

    /**
     * Create a Hasher that will uses the specified algorithm. 
     * @param algorithm the algorithm 
     */
    public Hasher(MessageDigest algorithm) {
        this.algorithm = algorithm;
    }

    /**
     * 
     * Create a Hasher that will uses the specified algorithm.
     * @param algorithm the algorithm to use.
     * @throws NoSuchAlgorithmException if the specified algorithm is not a valid algorithm for message hashing.
     */
    public Hasher(String algorithm) throws NoSuchAlgorithmException {
        this(MessageDigest.getInstance(algorithm));
    }

    /**
     * 
     * Create a Hasher that will uses the specified algorithm.
     * @param algorithm the algorithm to use.
     * @param provider the provider.
     * @throws NoSuchAlgorithmException if the specified algorithm is not a valid algorithm for message hashing.
     */
    public Hasher(String algorithm, Provider provider) throws NoSuchAlgorithmException {
        this(MessageDigest.getInstance(algorithm, provider));
    }

    /**
     * 
     * Create a Hasher that will uses the specified algorithm.
     * @param algorithm the algorithm to use.
     * @param provider the name of the provider.
     * @throws NoSuchAlgorithmException if the specified algorithm is not a valid algorithm for message hashing.
     * @throws NoSuchProviderException - if the specified provider is not registered in the security provider list. 
     * @throws IllegalArgumentException - if the provider name is null or empty.
     */
    public Hasher(String algorithm, String provider) throws NoSuchAlgorithmException, NoSuchProviderException {
        this(MessageDigest.getInstance(algorithm, provider));
    }

    /** 
     * Set the algorithm of the hasher.
     * @param algorithm the algorithm to set.
     */
    public void setAlgorithm(MessageDigest algorithm) {
        this.algorithm = algorithm;
    }

    /**
     * Set the algorithm of the hasher.
     * @param algorithm the algorithm to set.
     * @throws NoSuchAlgorithmException if the specified algorithm is not a valid algorithm for message hashing.
     */
    public void setAlgorithm(String algorithm) throws NoSuchAlgorithmException {
        setAlgorithm(MessageDigest.getInstance(algorithm));
    }

    /**
     * Set the algorithm of the hasher.
     * @param algorithm the algorithm to set.
     * @param provider the name of the provider.
     * @throws NoSuchAlgorithmException if the specified algorithm is not a valid algorithm for message hashing.
     */
    public void setAlgorithm(String algorithm, Provider provider) throws NoSuchAlgorithmException {
        setAlgorithm(MessageDigest.getInstance(algorithm, provider));
    }

    /**
     * Set the algorithm of the hasher.
     * @param algorithm the algorithm to set.
     * @param provider the name of the provider.
     * @throws NoSuchAlgorithmException if the specified algorithm is not a valid algorithm for message hashing.
     * @throws NoSuchProviderException - if the specified provider is not registered in the security provider list. 
     * @throws IllegalArgumentException - if the provider name is null or empty.
     */
    public void setAlgorithm(String algorithm, String provider) throws NoSuchAlgorithmException, NoSuchProviderException {
        setAlgorithm(MessageDigest.getInstance(algorithm, provider));
    }

    /** 
     * Returns the algorithm. 
     * @return the algorithm 
     */
    public MessageDigest getAlgorithm() {
        return algorithm;
    }

    /** 
     * Hashes the specified file to a bytes array.
     * @param file the file to hash. 
     * @return the hash as a bytes array .
     * @throws FileNotFoundException if the file to hash doesn't exist.
     * @throws IOException if an error occurs during reading the file 
     * to hash.
     */
    public byte[] hash(File file) throws FileNotFoundException, IOException {
        FileInputStream fis = new FileInputStream(file);
        byte[] hash = null;
        try {
            hash = hash(fis);
        } finally {
            fis.close();
        }
        return hash;
    }

    /** 
     * Hashes the specified input stream. 
     * @param is the input stream to hash 
     * @return the hash as a bytes array .
     * @throws IOException if an error occurs during reading the stream 
     * to hash.
     */
    public byte[] hash(InputStream is) throws IOException {
        BufferedInputStream bis = new BufferedInputStream(is);
        algorithm.reset();
        byte[] data = new byte[2048];
        int nbRead = 0;
        while ((nbRead = bis.read(data)) > 0) {
            algorithm.update(data, 0, nbRead);
        }
        return algorithm.digest();
    }

    /**
     * Hashes the specified datas under bytes array format.
     * @param data the datas to hash.
     * @return the hash under bytes array format.
     **/
    public byte[] hash(byte[] data) {
        algorithm.reset();
        algorithm.update(data);
        return algorithm.digest();
    }
}
